package com.ajz.common.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.SSLContext;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.CookieOrigin;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.impl.cookie.BestMatchSpec;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;


@SuppressWarnings("deprecation")
class AnyTrustStrategy implements TrustStrategy{
	
	@Override
	public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
		return true;
	}
	
}

public class HttpUtil {

	private static final Log log= LogFactory.getLog(HttpUtil.class);

	private static int bufferSize= 1024;

	private static volatile HttpUtil instance;
	
	private ConnectionConfig connConfig;

	private SocketConfig socketConfig;

	private ConnectionSocketFactory plainSF;

	private KeyStore trustStore;

	private SSLContext sslContext;

	private LayeredConnectionSocketFactory sslSF;

	private Registry<ConnectionSocketFactory> registry;

	private PoolingHttpClientConnectionManager connManager;

	private volatile HttpClient client;

	private volatile BasicCookieStore cookieStore;

	public static String defaultEncoding= "UTF-8";
	
	@SuppressWarnings("unused")
	private static StringEntity params2StringEntityConverter(Object params, String encoding) {
		if (encoding != null) {
			return new StringEntity(params2JSONStringConverter(params), encoding);
		} else {
			return new StringEntity(params2JSONStringConverter(params), defaultEncoding);
		}
	}
	
	private static String params2JSONStringConverter(Object params) {
		if (params instanceof List) {
			return JSONArray.fromObject(params).toString();
		} else {
			return JSONObject.fromObject(params).toString();
		}
	}

	private static UrlEncodedFormEntity params2UrlEncodedFormEntityConverter(Map<String, String> params, String encoding) throws UnsupportedEncodingException{
		if (encoding != null) {
			return new UrlEncodedFormEntity(params2NameValuePairListConverter(params), encoding);
		} else {
			return new UrlEncodedFormEntity(params2NameValuePairListConverter(params), defaultEncoding);
		}
	}
	
	private static List<NameValuePair> params2NameValuePairListConverter(Map<String, String> params){
		List<NameValuePair> nvps = new LinkedList<NameValuePair>();
		Set<Entry<String, String>> paramsSet= params.entrySet();
		for (Entry<String, String> paramEntry : paramsSet) {
			nvps.add(new BasicNameValuePair(paramEntry.getKey(), paramEntry.getValue()));
		}
		return nvps;
	}

	public static String readStream(InputStream in, String encoding){
		if (in == null){
			return null;
		}
		try {
			InputStreamReader inReader= null;
			if (encoding == null){
				inReader= new InputStreamReader(in, defaultEncoding);
			}else{
				inReader= new InputStreamReader(in, encoding);
			}
			char[] buffer= new char[bufferSize];
			int readLen= 0;
			StringBuffer sb= new StringBuffer();
			while((readLen= inReader.read(buffer))!=-1){
				sb.append(buffer, 0, readLen);
			}
			inReader.close();
			return sb.toString();
		} catch (IOException e) {
			log.error("读取返回内容出错", e);
		}
		return null;
	}

	@SuppressWarnings("deprecation")
	private HttpUtil(){
		//设置连接参数
		connConfig = ConnectionConfig.custom().setCharset(Charset.forName(defaultEncoding)).build();
		socketConfig = SocketConfig.custom().setSoTimeout(100000).build();
		RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
		plainSF = new PlainConnectionSocketFactory();
		registryBuilder.register("http", plainSF);
		//指定信任密钥存储对象和连接套接字工厂
		try {
			trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
			sslContext = SSLContexts.custom().useTLS().loadTrustMaterial(trustStore, new AnyTrustStrategy()).build();
			sslSF = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
			registryBuilder.register("https", sslSF);
		} catch (KeyStoreException e) {
			throw new RuntimeException(e);
		} catch (KeyManagementException e) {
			throw new RuntimeException(e);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
		registry = registryBuilder.build();
		//设置连接管理器
		connManager = new PoolingHttpClientConnectionManager(registry);
		connManager.setDefaultConnectionConfig(connConfig);
		connManager.setDefaultSocketConfig(socketConfig);
		//指定cookie存储对象
		cookieStore = new BasicCookieStore();
		//构建客户端
		client= HttpClientBuilder.create().setDefaultCookieStore(cookieStore).setConnectionManager(connManager).build();
	}

	public static HttpUtil getInstance(){
		synchronized (HttpUtil.class) {
			if (HttpUtil.instance == null){
				instance = new HttpUtil();
			}
			return instance;
		}
	}

	public InputStream doGet(String url) throws URISyntaxException, ClientProtocolException, IOException{
		HttpResponse response= this.doGet(url, null);
		return response!=null ? response.getEntity().getContent() : null;
	}

	public String doGetForString(String url) throws URISyntaxException, ClientProtocolException, IOException{
		return HttpUtil.readStream(this.doGet(url), null);
	}

	public InputStream doGetForStream(String url, Map<String, String> queryParams) throws URISyntaxException, ClientProtocolException, IOException{
		HttpResponse response= this.doGet(url, queryParams);
		return response!=null ? response.getEntity().getContent() : null;
	}

	public String doGetForString(String url, Map<String, String> queryParams) throws URISyntaxException, ClientProtocolException, IOException{
		return HttpUtil.readStream(this.doGetForStream(url, queryParams), null);
	}

	/**
	 * 基本的Get请求
	 * @param url 请求url
	 * @param queryParams 请求头的查询参数
	 * @return
	 * @throws URISyntaxException 
	 * @throws IOException 
	 * @throws ClientProtocolException 
	 */
	public HttpResponse doGet(String url, Map<String, String> queryParams) throws URISyntaxException, ClientProtocolException, IOException{
		HttpGet gm = new HttpGet();
		URIBuilder builder = new URIBuilder(url);
		//填入查询参数
		if (queryParams!=null && !queryParams.isEmpty()){
			builder.setParameters(HttpUtil.params2NameValuePairListConverter(queryParams));
		}
		gm.setURI(builder.build());
		return client.execute(gm);
	}

	public InputStream doPostForStream(String url, Map<String, String> queryParams) throws URISyntaxException, ClientProtocolException, IOException {
		HttpResponse response = this.doPostUEFE(url, queryParams, null);
		return response!=null ? response.getEntity().getContent() : null;
	}

	public String doPostForString(String url, Map<String, String> queryParams) throws URISyntaxException, ClientProtocolException, IOException {
		return HttpUtil.readStream(this.doPostForStream(url, queryParams), null);
	}

	public InputStream doPostRetStream(String url, Map<String, String> queryParams, Map<String, String> formParams) throws URISyntaxException, ClientProtocolException, IOException{
		HttpResponse response = this.doPostUEFE(url, queryParams, formParams);
		return response!=null ? response.getEntity().getContent() : null;
	}

	public String doPostRetString(String url, Map<String, String> queryParams, Map<String, String> formParams) throws URISyntaxException, ClientProtocolException, IOException{
		return HttpUtil.readStream(this.doPostRetStream(url, queryParams, formParams), null);
	}

	/**
	 * 
	 * Method Description:参数为键值风格的Post请求
	 * @param url 请求url
	 * @param formParams post表单的参数
	 * @return HttpResponse
	 * @throws URISyntaxException
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @Time 2017年6月14日 下午7:50:01  @author Wy
	 */
	public HttpResponse doPostUEFE(String url, Map<String, String> formParams) throws URISyntaxException, ClientProtocolException, IOException{
		return doPostUEFE(url, null, formParams);
	}
	/**
	 * 参数为键值风格的Post请求
	 * @param url 请求url
	 * @param queryParams 请求头的查询参数
	 * @param formParams post表单的参数
	 * @return HttpResponse
	 * @throws URISyntaxException 
	 * @throws IOException 
	 * @throws ClientProtocolException 
	 */
	public HttpResponse doPostUEFE(String url, Map<String, String> queryParams, Map<String, String> formParams) throws URISyntaxException, ClientProtocolException, IOException{
		HttpPost hPost = new HttpPost();
		URIBuilder builder = new URIBuilder(url);
		//填入查询参数
		if (queryParams!=null && !queryParams.isEmpty()){
			builder.setParameters(HttpUtil.params2NameValuePairListConverter(queryParams));
		}
		hPost.setURI(builder.build());
		//填入表单参数
		if (formParams!=null && !formParams.isEmpty()){
			hPost.setEntity(HttpUtil.params2UrlEncodedFormEntityConverter(formParams, defaultEncoding));
		}
		return client.execute(hPost);
	}
	
	/**
	 * 
	 * Method Description:参数为JSON风格的Post请求
	 * @param url 请求url
	 * @param formParams post表单的参数
	 * @return HttpResponse
	 * @throws URISyntaxException
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @Time 2017年6月14日 下午7:50:01  @author Wy
	 */
	public HttpResponse doPostStrE(String url, Object formData) throws URISyntaxException, ClientProtocolException, IOException{
		return doPostStrE(url, null, formData);
	}
	
	/** 
	* 参数为JSON风格的Post请求 
	* @param url 请求url 
	* @param queryParams 请求头的查询参数 
	* @param formData 直接放入post请求体中的文本(请使用JSON) 
	* @return HttpResponse
	* @throws URISyntaxException  
	* @throws UnsupportedEncodingException  
	*/  
	public HttpResponse doPostStrE(String url, Map<String, String> queryParams, Object formData) throws URISyntaxException, ClientProtocolException, IOException{  
	    HttpPost hPost = new HttpPost();  
	    URIBuilder builder = new URIBuilder(url);  
	    //填入查询参数  
	    if (MapUtils.isNotEmpty(queryParams)){  
	        builder.setParameters(HttpUtil.params2NameValuePairListConverter(queryParams));  
	    }  
	    hPost.setURI(builder.build());  
	    //填入post jsonStr数据
	    if (formData != null){  
	        //下面的ContentType完整类名为:org.apache.http.entity.ContentType  
	    	hPost.setEntity(new StringEntity((String) formData, ContentType.APPLICATION_JSON));
//	        hPost.setEntity(HttpUtil.params2StringEntityConverter(formData, defaultEncoding));  
	    }  
	    return client.execute(hPost);  
	}  
	
	/**
	 * 多块Post请求
	 * @param url 请求url
	 * @param queryParams 请求头的查询参数
	 * @param formParts post表单的参数,支持字符串-文件(FilePart)和字符串-字符串(StringPart)形式的参数
	 * @param maxCount 最多尝试请求的次数
	 * @return
	 * @throws URISyntaxException 
	 * @throws ClientProtocolException 
	 * @throws HttpException
	 * @throws IOException
	 */
//	public HttpResponse multipartPost(String url, Map<String, String> queryParams, List<FormBodyPart> formParts) throws URISyntaxException, ClientProtocolException, IOException{
//		HttpPost hPost= new HttpPost();
//		URIBuilder builder = new URIBuilder(url);
//		//填入查询参数
//		if (queryParams!=null && !queryParams.isEmpty()){
//			builder.setParameters(HttpUtil.paramsConverter(queryParams));
//		}
//		hPost.setURI(builder.build());
//		//填入表单参数
//		if (formParts!=null && !formParts.isEmpty()){
//			MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
//			entityBuilder = entityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
//			for (FormBodyPart formPart : formParts) {
//				entityBuilder = entityBuilder.addPart(formPart.getName(), formPart.getBody());
//			}
//			hPost.setEntity(entityBuilder.build());
//		}
//		return client.execute(hPost);
//	}
	
	/**
	 * Method Description: 获取响应编码
	 * @param response 响应体
	 * @return null/响应编码
	 * @Time 2017年6月9日 下午3:44:54  @author Wy
	 */
	public String getRespCode(HttpResponse response) {
		return response!=null ? String.valueOf(response.getStatusLine().getStatusCode()) : null;
	}
	
	/**
	 * Method Description: 获取响应数据
	 * @param response 响应内容
	 * @return null/响应JSON数据
	 * @throws IOException 
	 * @throws ParseException 
	 * @Time 2017年6月9日 下午3:44:54  @author Wy
	 */
	public JSONObject getRespJSON(HttpResponse hResponse) throws ParseException, IOException {
		return hResponse!=null ? JSONObject.fromObject(respEntity2String(hResponse)) : null;
	}
	
	/**
	 * Method Description:获取（字符串）响应数据
	 * @param hResponse 响应内容
	 * @return null/响应数据
	 * @throws ParseException
	 * @throws IOException
	 * @Time 2017年7月25日 上午9:42:49  @author Wy
	 */
	public String respEntity2String(HttpResponse hResponse) throws ParseException, IOException {
		return EntityUtils.toString(hResponse.getEntity(), defaultEncoding);
	}
	
	/**
	 * Method Description:获取（JSON）响应数据
	 * @param rString （字符串）响应数据
	 * @return JSONObject
	 * @throws ParseException
	 * @throws IOException
	 * @Time 2017年7月25日 上午9:46:55  @author Wy
	 */
	public JSONObject respString2JSONObject(String rString) throws ParseException, IOException {
		return JSONObject.fromObject(rString);
	}

	/**
	 * 获取当前Http客户端状态中的Cookie
	 * @param domain 作用域
	 * @param port 端口 传null 默认80
	 * @param path Cookie路径 传null 默认"/"
	 * @param useSecure Cookie是否采用安全机制 传null 默认false
	 * @return
	 */
	@SuppressWarnings("deprecation")
	public Map<String, Cookie> getCookie(String domain, Integer port, String path, Boolean useSecure){
		if (domain == null){
			return null;
		}
		if (port==null){
			port= 80;
		}
		if (path==null){
			path="/";
		}
		if (useSecure==null){
			useSecure= false;
		}
		List<Cookie> cookies = cookieStore.getCookies();
		if (cookies==null || cookies.isEmpty()){
			return null;
		}

		CookieOrigin origin= new CookieOrigin(domain, port, path, useSecure);
		BestMatchSpec cookieSpec = new BestMatchSpec();
		Map<String, Cookie> retVal= new HashMap<String, Cookie>();
		for (Cookie cookie : cookies) {
			if(cookieSpec.match(cookie, origin)){
				retVal.put(cookie.getName(), cookie);				
			}
		}
		return retVal;
	}

	/**
	 * 批量设置Cookie
	 * @param cookies cookie键值对图
	 * @param domain 作用域 不可为空
	 * @param path 路径 传null默认为"/"
	 * @param useSecure 是否使用安全机制 传null 默认为false
	 * @return 是否成功设置cookie
	 */
	public boolean setCookie(Map<String, String> cookies, String domain, String path, Boolean useSecure){
		synchronized (cookieStore) {
			if (domain==null){
				return false;
			}
			if (path==null){
				path= "/";
			}
			if (useSecure==null){
				useSecure= false;
			}
			if (cookies==null || cookies.isEmpty()){
				return true;
			}
			Set<Entry<String, String>> set= cookies.entrySet();
			String key= null;
			String value= null;
			for (Entry<String, String> entry : set) {
				key= entry.getKey();
				if (key==null || key.isEmpty() || value==null || value.isEmpty()){
					throw new IllegalArgumentException("cookies key and value both can not be empty");
				}
				BasicClientCookie cookie= new BasicClientCookie(key, value);
				cookie.setDomain(domain);
				cookie.setPath(path);
				cookie.setSecure(useSecure);
				cookieStore.addCookie(cookie);
			}
			return true;
		}
	}

	/**
	 * 设置单个Cookie
	 * @param key Cookie键
	 * @param value Cookie值
	 * @param domain 作用域 不可为空
	 * @param path 路径 传null默认为"/"
	 * @param useSecure 是否使用安全机制 传null 默认为false
	 * @return 是否成功设置cookie
	 */
	public boolean setCookie(String key, String value, String domain, String path, Boolean useSecure){
		Map<String, String> cookies= new HashMap<String, String>();
		cookies.put(key, value);
		return setCookie(cookies, domain, path, useSecure);
	}

}
